<?php

class bobSourceView extends GtkScrolledWindow {

	public $fresh = true;
	public $signalEdit = false;
	
	public $curLine = 0;
	public $curMargin = 0;
	public $curTabWidth = 0;
	public $curEOL = "LF";

	static $type = array(
		'CRLF' => "\r\n",
		'LF' => "\n",
		'CR' => "\r"
	);

	public function __construct() {
		parent::__construct();

		$this->root = null;
		$this->signalRoot = $this->connect_simple(
			'map',
			array('bob','digRootUp'),
			$this
		);
		
		$this->set_policy(Gtk::POLICY_ALWAYS,Gtk::POLICY_ALWAYS);
		$this->set_shadow_type(Gtk::SHADOW_IN);
		$this->set_border_width(3);
		
		$this->view = new GtkSourceView;

		$this->view->set_show_line_numbers(true);
		$this->view->set_auto_indent(true);
		$this->view->set_margin(($this->curMargin = bob::$config->get('pos-right-margin')));
		$this->view->set_tabs_width(($this->curTabWidth = bob::$config->get('tab-width')));
		$this->view->connect('expose-event',array($this,'draw_current_line'));
		
		$this->codelang = bob::$langman->get_language(
			'jenny/php'		
		);	

		$this->buffer = new GtkSourceBuffer;
		
		$this->buffer->set_language($this->codelang);
		$this->buffer->set_highlight(true);
		$this->view->set_buffer($this->buffer);

		$this->add($this->view);
		$this->show_all();
		bob::runUI();

		$table = $this->buffer->get_tag_table();
		$table->foreach(array($this,'apply_custom_style'));

		return;
	}
	
	public function detect_line_ending() {
		
		$text = $this->buffer->get_text(
			$this->buffer->get_start_iter(),
			$this->buffer->get_end_iter()
		);
		
		foreach(self::$type as $end => $char) {
			if(preg_match("/{$char}$/",$text)) {
				$this->curEOL = $end;
				break;
			}
		}
		
		if($this->curEOL != 'LF') {
			//. strip anything stupid out for editing, and i guess will have
			//. to reinsert them on save.
			$text = str_replace(self::$type[$this->curEOL],"\n",$text);
			$this->buffer->set_text($text);
		}
		
		$this->root->on_eol_change($this->curEOL);
		
		return;
	}
	
	public function get_savable() {
	
		$text = $this->buffer->get_text(
			$this->buffer->get_start_iter(),
			$this->buffer->get_end_iter()
		);

		if(WINDOWS)
			$text = str_replace("\r\n","\n",$text);
		
		if($this->curEOL != 'LF') {
			$text = str_replace("\n",self::$type[$this->curEOL],$text);
		}
		
		return $text;
	}
	
	public function manual_init() {
		$vstyle = $this->view->get_style();
		$this->vstyle = $vstyle->copy();

		$this->vstyle->bg[Gtk::STATE_SELECTED] = GdkColor::parse(bob::$config->get('colour-current-line'));
		$this->vstyle->bg[Gtk::STATE_NORMAL] = GdkColor::parse(bob::$config->get('colour-right-margin'));

		$this->vstyle->attach($this->view->get_window(Gtk::TEXT_WINDOW_TEXT));
		return;	
	}
	
	public function apply_custom_style($tag) {

		$style = array();
		$line = explode(chr(10),trim(file_get_contents("{$GLOBALS['PATH_SYNTAX']}/default.style")));
		foreach($line as $raw) {
			list($name,$foreground,$background,$bold,$italic) = explode(':',$raw);
			$style[$name] = array($foreground,$background,$bold,$italic);
		}

		$this->view->modify_text(Gtk::STATE_NORMAL,GdkColor::parse($style['normal'][0]));
		$this->view->modify_font(new PangoFontDescription('Bitstream Vera Sans Mono 8'));
		bob::runUI();
		
		$tagname = $tag->get_property('name');
		if(array_key_exists($tagname,$style)) {
			if($colour = $style[$tagname][0]) {
				$tag->set_property('foreground-gdk',GdkColor::parse($colour));
			}
			
			if($colour = $style[$tagname][1]) {
				$tag->set_property('background-gdk',GdkColor::parse($colour));
			}
			
			if($colour = $style[$tagname][2]) {
				$tag->set_property('weight',700);
			} else {
				$tag->set_property('weight',0);
			}
			
			if($colour = $style[$tagname][3]) {
				$tag->set_property('style',Pango::STYLE_ITALIC);
			} else {
				$tag->set_property('style',Pango::STYLE_NORMAL);
			}
		}
		
		unset($style,$line,$raw);
		return;
	}

	public function draw_current_line($w,$e) {
	
		/*//. custom current line and right margin drawing function.
		  ../ thanks to Bluefish for this, I just translated it to
		  ../ PHP with slight modifications only. goal is to allow
		  ../ 		$text = $this->buffer->get_text(
			$this->buffer->get_start_iter(),
			$this->buffer->get_end_iter()
		);for custom colour options.
		.//*/

		bob::runUI();
		
		if(!array_key_exists('vstyle',$this)) { return; }

		$gdkleft = $this->view->get_window(Gtk::TEXT_WINDOW_LEFT);
		$gdktext = $this->view->get_window(Gtk::TEXT_WINDOW_TEXT);
		
		if($gdkleft === $e->window) {
			//. i assume to draw on the numbers.
			//. whichever this seemd to fix a laying problem with the text
			//. and the current line highlight.
		}
		
		else if($gdktext === $e->window) {
		
			if(!$this->view->is_visible()) { return; }

			$iter = $this->buffer->get_iter_at_mark($this->buffer->get_insert());
					
			if(!$this->signalRoot) {
			
				//. find out how many tabs are in this line so that the char
				//. count thing ends up right.
			
				$this->curLine = $iter->get_line() + 1;
				$this->curChar = $iter->get_line_offset() + 1;
				$this->root->statusbar->message(
					'viewer',
					"<b>EOL</b> {$this->curEOL} | ".
						"<b>Ln</b> {$this->curLine} | ".
						"<b>Chr</b> {$this->curChar}"
				);
			}
			
			$rect = $this->view->get_visible_rect();
			list($w,$w2) = $this->view->get_line_yrange($iter);
			list($x,$y) = $this->view->buffer_to_window_coords(Gtk::TEXT_WINDOW_TEXT,$rect->x,$rect->y);
			list($null,$w) = $this->view->buffer_to_window_coords(Gtk::TEXT_WINDOW_TEXT,0,$w);
	
			$gdktext->draw_rectangle(
				$this->vstyle->bg_gc[Gtk::STATE_SELECTED],
				true,
				$x,
				$w, $rect->width, $w2
			);
			
			$l = $this->view->create_pango_layout('x');
			$w = $l->get_pixel_size();
	
			$gdktext->draw_line(
				$this->vstyle->bg_gc[Gtk::STATE_NORMAL],
				($this->curMargin * $w[0]), $y,
				($this->curMargin * $w[0]), ($y + $rect->height)		
			);
		}
		
		bob::runUI();
	}
	
	public function current_iter() {
		return $this->buffer->get_iter_at_mark($this->buffer->get_insert());
	}

	public function start_iter() {
		return $this->buffer->get_start_iter();
	}
	
	public function end_iter() {
		return $this->buffer->get_end_iter();
	}

}

class bobNotebook extends GtkNotebook {
	public function __construct() {
		parent::__construct();
		$this->set_scrollable(true);
		$this->show_all();
		return;
	}
}

class bobNotebookLabel extends GtkLabel {
	public function __construct($label,$file) {
		parent::__construct($label);
		$this->file = $file;
		
		$style = $this->get_style();
	
		// this is the currently selected tab.
		$this->oldColourNormal = GdkColor::parse(sprintf(
			'#%02x%02x%02x',
			$style->fg[Gtk::STATE_NORMAL]->red,
			$style->fg[Gtk::STATE_NORMAL]->green,
			$style->fg[Gtk::STATE_NORMAL]->blue
		));

		// this is for the other tabs.
		$this->oldColourActive = GdkColor::parse(sprintf(
			'#%02x%02x%02x',
			$style->fg[Gtk::STATE_ACTIVE]->red,
			$style->fg[Gtk::STATE_ACTIVE]->green,
			$style->fg[Gtk::STATE_ACTIVE]->blue
		));
		
		return;
	}
	
	public function set_glow($on = true) {
		if($on) {
			$this->modify_fg(Gtk::STATE_NORMAL,GdkColor::parse('#ff8a00'));
			$this->modify_fg(Gtk::STATE_ACTIVE,GdkColor::parse('#dd6800'));
		} else {
			$this->modify_fg(Gtk::STATE_NORMAL,$this->oldColourNormal);
			$this->modify_fg(Gtk::STATE_ACTIVE,$this->oldColourActive);
		}
	}
}

class bobWindow extends GtkWindow {

	public $curPage,$curSrcView,$curTabLabel,$curFile;
	public $srcview = array();
	public $width,$height,$x,$y;
	public $keyMod = 0;
	
	// this windows autocomp signal.
	public $signalAutoComp;
	//. this windows autocomp status.
	public $autocomp;
	//. this windows autocomp matches.
	public $automatch;
	public $automatchnum;
	//. this windows autocomp search.
	public $autosearch;

	public function __construct($register = true) {
		parent::__construct();
		
		$this->vbox = new GtkVBox;
		$this->spacer = new GtkVBox;
		$this->notebook = new bobNotebook;
		$this->statusbar = new bobStatusBar;
		$this->searchbar = new bobSearchBar;
		$this->replacebar = new bobReplaceBar;
		$this->quickopbar = new bobQuickOptionsBar;
		$this->sidebar = new bobSidebar;
		
		$this->menubar = new bobMenuBar(array(
			'file' => array('type'=>'menu','parent'=>null,'text'=>'File'),
			'file-new' => array('type'=>'item','parent'=>'file-bobMenu','text'=>'New','action'=>array($this,'mkNeuTab')),
			'file-open' => array('type'=>'item','parent'=>'file-bobMenu','text'=>'Open File...','action'=>array($this,'on_action_open')),
			'break01' => array('type'=>'break','parent'=>'file-bobMenu'),
			'file-recent' => array('type'=>'header','parent'=>'file-bobMenu','text'=>'Recent Files'),
			'file-recent-list' => array('type'=>'recent-list','parent'=>'file-bobMenu'),
			'break02' => array('type'=>'break','parent'=>'file-bobMenu'),
			'file-close-current' => array('type'=>'item','parent'=>'file-bobMenu','text'=>'Close Current','action'=>array($this,'on_action_close')),
//			'file-close-all' => array('type'=>'item','parent'=>'file-bobMenu','text'=>'Close All','action'=>array($this,'on_action_close_all')),
//			'break03' => array('type'=>'break','parent'=>'file-bobMenu'),
			'file-quit' => array('type'=>'item','parent'=>'file-bobMenu','text'=>'Quit','action'=>array('bob','endProgram')),

			'edit' => array('type'=>'menu','parent'=>null,'text'=>'Edit'),
			'edit-search' => array('type'=>'item','parent'=>'edit-bobMenu','text'=>'Search...','action'=>array($this,'on_action_searchbar',true)),
			'edit-replace' => array('type'=>'item','parent'=>'edit-bobMenu','text'=>'Search and Replace...','action'=>array($this,'on_action_replacebar',true)),
			'break04' => array('type'=>'break','parent'=>'edit-bobMenu'),
			'edit-eol' => array('type'=>'eol-list','parent'=>'edit-bobMenu','text'=>'Line Endings'),

			'project' => array('type'=>'menu','parent'=>null,'text'=>'Project'),
			'help' => array('type'=>'menu','parent'=>null,'text'=>'Help','align'=>'right'),
			'time' => array('type'=>'time','parent'=>null,'align'=>'right')	
		));
				
		$this->toolbar = new bobToolBar(array(
			'new' => array('type'=>'button','text'=>'Neu','icon'=>Gtk::STOCK_NEW,'action'=>array($this,'mkNeuTab')),
			'open' => array('type'=>'button','text'=>'Open','icon'=>Gtk::STOCK_OPEN,'action'=>array($this,'on_action_open')),
			'break01' => array('type'=>'break'),
			'save' => array('type'=>'button','text'=>'Save','icon'=>Gtk::STOCK_SAVE,'action'=>array($this,'on_action_save')),
			'save-as' => array('type'=>'button','text'=>'Save As...','icon'=>Gtk::STOCK_SAVE_AS,'action'=>array($this,'on_action_save_as')),
			'break02' => array('type'=>'break'),
			'search' => array('type'=>'togglebutton','text'=>'Search','icon'=>Gtk::STOCK_FIND,'action'=>array($this,'on_action_searchbar')),
			'replace' => array('type'=>'togglebutton','text'=>'Replace','icon'=>Gtk::STOCK_FIND_AND_REPLACE,'action'=>array($this,'on_action_replacebar')),
			'divide01' => array('type'=>'divide'),
			'goto-line' => array('type'=>'line-jump-bar','action'=>array($this,'on_line_jump')),
			'break04' => array('type'=>'break'),
			'quickop' => array('type'=>'togglebutton','text'=>'Quick Options','icon'=>Gtk::STOCK_PREFERENCES,'action'=>array($this,'on_action_quickop')),
			'break03' => array('type'=>'break'),
			'close' => array('type'=>'button','text'=>'Close','icon'=>Gtk::STOCK_CLOSE,'action'=>array($this,'on_action_close')),
		));

		$this->width = bob::$config->get('window-width');
		$this->height = bob::$config->get('window-height');	
		$this->x = bob::$config->get('window-pos-x');
		$this->y = bob::$config->get('window-pos-y');

		if(!$this->width || !$this->height) {
			$this->width = 640;
			$this->height = 480;
		}
		
		$this->resize($this->width,$this->height);
		$this->set_icon_from_file("{$GLOBALS['PATH_IMAGES']}/icon.png");
		$this->set_titlebar(null);

		$this->connect('configure-event',array($this,'on_resize'));
		$this->connect('key-press-event',array($this,'on_key'));
		$this->connect_simple('delete-event',array($this,'on_close'));

		$this->notebook->connect('switch-page',array($this,'on_page_change'));
		$this->notebook->connect('page-reordered',array($this,'on_page_reorder'));

		$this->searchbar->entry->child->connect('key-press-event',array($this,'on_key_searchbox'));
		$this->searchbar->find->connect_simple('clicked',array($this,'on_key_searchbox'),null,null);
		$this->searchbar->close->connect_simple('clicked',array($this,'on_action_searchbar'));

		$this->replacebar->entry->child->connect('key-press-event',array($this,'on_key_replacebox'));
		$this->replacebar->run->connect_simple('clicked',array($this,'on_key_replacebox'),null,null);
		$this->replacebar->close->connect_simple('clicked',array($this,'on_action_replacebar'));		
		
		//. register this window.
		if($register) {
			bob::windowRegister($this);
		}

		//. setup drag and drop.
		$this->vbox->drag_dest_set(
			Gtk::DEST_DEFAULT_ALL,
			array(array('text/uri-list',0,0)),
			(Gdk::ACTION_COPY | Gdk::ACTION_MOVE)
		); $this->vbox->connect(
			"drag-data-received",
			array($this,'on_recieve_drop')
		);
		
		$this->vbox->pack_start($this->menubar,false,false,0);
		$this->vbox->pack_start($this->toolbar,false,false,0);
		$this->vbox->pack_start($this->quickopbar,false,false,0);
		$this->vbox->pack_start($this->spacer,false,false,1);		
		$this->vbox->pack_start($this->notebook,true,true,0);
		$this->vbox->pack_start($this->searchbar,false,false,0);
		$this->vbox->pack_start($this->replacebar,false,false,0);
		$this->vbox->pack_start($this->statusbar,false,false,0);	
		$this->add($this->vbox);

		$this->spacer->show();
		$this->vbox->show();
		$this->show();
		bob::runUI();
		
		if($this->x >= 0 && $this->y >= 0) {
			$this->move($this->x,$this->y);
		} else {
			$this->set_position(Gtk::WIN_POS_CENTER);
		}

		$key = $this->mkNeuTab();
		return;
	}
	
	public function mkNeuTab($file = null) {

		//. generate a new 'file' for this tab.
		if(!$file)
			$file = sprintf('Neu%d',bob::$fileIter++);
		
		//. build a new text view widget and bind it to the main window
		//. signal processors.
		$this->srcview[$file] = new bobSourceView;
		
		$this->srcview[$file]->curEOL = bob::$config->get('end-of-line');
		
		//. when the file is edited, mark it as so and disconnect the signal.
		$this->srcview[$file]->signalEdit = $this->srcview[$file]->buffer->connect_simple(
			'changed',array($this,'on_file_edit')
		);
		
		// catch all keystrokes and clicks on the text widget.
		$this->srcview[$file]->signalKey = $this->srcview[$file]->view->connect(
			'key-release-event',array($this,'on_key_srcview')
		);
		$this->srcview[$file]->signalButton = $this->srcview[$file]->view->connect(
			'button-press-event',array($this,'on_key_srcview')
		);

		// add a new tab with this text widget with and advanecd text label.
		$id = $this->notebook->append_page(
			$this->srcview[$file],
			new bobNotebookLabel(basename($file),$file)
		);
		$page = $this->notebook->get_nth_page($id);
		$this->notebook->set_tab_reorderable($page,true);		
		$this->notebook->show_all();
		
		bob::runUI();

		//. change to this new page.
		$this->notebook->set_current_page($id);
		
		//. configure initial options for this page.		
		$this->srcview[$file]->manual_init();
		$this->srcview[$file]->view->grab_focus();

		bob::runUI();

		$this->on_tab_change($this->srcview[$file]->curTabWidth);
/*
		$this->quickopbar->margin->spin->set_value(
			$this->srcview[$file]->curMargin
		);
		$this->quickopbar->tab->spin->set_value(
			$this->srcview[$file]->curTabWidth
		);
*/		
		return $file;
	}
	
	public function openFile($file) {
		if(array_key_exists($file,$this->srcview)) {
			$this->notebook->set_current_page($this->notebook->page_num($this->srcview[$file]));
			return;
		}	
		
		if(substr($this->curFile,0,3) != 'Neu' && !file_exists($file)) {
			return;
		}
		
		if(substr($this->curFile,0,3) == 'Neu' && !$this->curSrcView->buffer->get_char_count()) {
			//. if the current tab is still new and empty then reuse it for
			//. the requested file, relink the sourceview by filename, and
			//. update tab info.	
			$this->srcview[$file] = $this->srcview[$this->curFile];
			unset($this->srcview[$this->curFile]);

			$this->curFile = $this->curTabLabel->file = $file;
			$this->curTabLabel->set_text(basename($file));
		} else {
			//. else open this file in a new tab.
			$this->mkNeuTab($file);
			bob::runUI();
		}
		
		bob::arrayShoveFront(bob::$histFile,$file);
		bob::$config->set(
			'file-history',
			bob::writeHistoryObject('file-history',bob::$histFile)
		); bob::$config->save();
		
		$this->menubar->item['file-recent-list']->update();

		$this->curSrcView->buffer->begin_not_undoable_action();
		$this->curSrcView->buffer->set_text(file_get_contents($file));
		$this->curSrcView->buffer->set_modified(false);
		$this->curSrcView->fresh = true;
		$this->curSrcView->detect_line_ending();
		$this->curSrcView->signalEdit = $this->curSrcView->buffer->connect_simple(
			'changed',array($this,'on_file_edit')
		);
		$this->curSrcView->buffer->end_not_undoable_action();
		$this->curTabLabel->set_glow(false);
		$this->curSrcView->buffer->place_cursor($this->curSrcView->buffer->get_start_iter());
		$this->on_page_change(null,null,$this->curPage);
		return;
	}

	/*//////////////////.
		mutator methods for window/sourceview options.
	                                                      .//////////////////*/

	/*//. void on_eol_change(string eol);
	  ../ change the end-of-line type for the current file.
	.//*/
	public function on_eol_change($eol) {
		if(!$this->curSrcView) { return; }
	
		//. update the eol for this sourceview.
		$this->curSrcView->curEOL = $eol;
		
		//. update the quick options bar.
		$this->quickopbar->eol->drop->set($eol);
		
		//. update the edit > line ending menu options.
		$this->menubar->item['edit-eol']->item[$eol]->set_active(true);
		
		//. update the config file.
		//bob::$config->set('end-of-line',$eol);
		
		$this->curSrcView->view->grab_focus();		
		return;
	}

	/*//. void on_tab_change(int tabwidth);
	  ../ adjust the tab width for the current file.
	.//*/
	public function on_tab_change($tab) {
		if(!$this->curSrcView) { return; }

		//. update the quick options bar.
		$this->quickopbar->tab->spin->set_value($tab);

		//. update the current sourceview.
		$this->curSrcView->curTabWidth = $tab;
		$this->curSrcView->view->set_tabs_width($tab);
		
		$this->curSrcView->view->grab_focus();
		return;
	}

	/*//. void on_margin_change(int margin);
	  ../ adjust the right margin marker for the current file.
	.//*/
	public function on_margin_change($margin) {
		if(!$this->curSrcView) { return; }

		//. update the quick options bar.
		$this->quickopbar->margin->spin->set_value($margin);

		//. update the current sourceview.
		$this->curSrcView->curMargin = $margin;
		$this->curSrcView->view->set_margin($margin);
		
		$this->curSrcView->view->grab_focus();
		return;
	}

	/*//////////////////.
		file operation methods.
	                                                      .//////////////////*/

	public function on_action_open() {	
		$dialog = new bobFileOpenDialog($this);
		
		if($dialog->file) {
			foreach($dialog->file as $file) {
				$this->openFile($file);
			}
		}
		
		$dialog->destroy();
		unset($dialog);
		return;
	}
	
	public function on_action_save() {
		if(substr($this->curFile,0,3) === 'Neu') {
			//. if this is still a new file propt for a filename to save as.
			$this->on_action_save_as(null);
		} else {
			//. else save current file.
			$this->on_action_save_as($this->curFile);
		}
		return;
	}

	public function on_action_save_as($file = null) {
		if(!$file) {
			$dialog = new bobFileSaveDialog($this);
			$file = $dialog->file;
			$dialog->destroy();
			unset($dialog);
		}
		
		if($file) {
			bob::debugfln('saving %s',$file);
			list($start,$stop) = $this->curSrcView->buffer->get_bounds();
					
			file_put_contents($file,$this->curSrcView->get_savable());
			
			$this->curTabLabel->set_glow(false);
			$this->curSrcView->buffer->set_modified(false);
			$this->curSrcView->fresh = true;
			$this->curSrcView->signalEdit = $this->curSrcView->buffer->connect_simple(
				'changed',array($this,'on_file_edit')
			);
		
			if($file != $this->curFile) {
				$this->srcview[$file] = $this->srcview[$this->curFile];
				unset($this->srcview[$this->curFile]);
	
				$this->curFile = $this->curTabLabel->file = $file;
				$this->curTabLabel->set_text(basename($file));
				$this->on_page_change(null,null,$this->curPage);
			}	
		}
		
		return;
	}

	public function on_action_close() {
		$file = $this->curFile;
		$do = true;
		
		//. filename was copied because after killing, curFile gets edited too
		//. fast behind-the-scenes by gtk.

		//. if this file has been modified to not close it unless i am sure i
		//. do not want it.
		if(!$this->curSrcView->fresh) {
			$dialog = new bobWarnUnsavedDialog($this);
			
			if(!$dialog->accept)
				$do = false;
			
			if($dialog->save) {
				$this->on_action_save();
				$file = $this->curFile;
			}
			
			$dialog->destroy();
			unset($dialog);
		}
	
		if($do) {
			//. deleting the sourceview deleted the tab. pretty cool.
			$this->srcview[$file]->destroy();
			unset($this->srcview[$file]);
		
			//. i always want a tab. if the last tab was killed then make a
			//. new one to a blank file.
			if(!count($this->srcview))
				$this->mkNeuTab();
		}
		
		return;
	}
	
	public function on_action_tab_next($right,$move) {
		$cur = $this->notebook->get_current_page();
		$cnt = $this->notebook->get_n_pages();
		if($right) {
			if(++$cur == $cnt) {
				$cur = 0;
			}
		} else {
			if(--$cur == -1) {
				$cur = $cnt-1;
			}
		}
		
		if($move) {
			$this->notebook->reorder_child($this->curSrcView,$cur);
		} else {
			$this->notebook->set_current_page($cur);
		}
		return;
	}
	
	public function on_key($w,$e) {
		$return = false;
		bob::runUI();
		switch($e->keyval) {
			case(Gdk::KEY_Left): {
				if(bob::keyMod($e->state,(Gdk::CONTROL_MASK|Gdk::MOD1_MASK))) {
					//. move tab left.
					$this->on_action_tab_next(false,true);
					$return = true;				
				}
				else if(bob::keyMod($e->state,Gdk::CONTROL_MASK)) {
					//. select next left tab.
					$this->on_action_tab_next(false,false);
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Right): {
				if(bob::keyMod($e->state,(Gdk::CONTROL_MASK|Gdk::MOD1_MASK))) {
					//. move tab right.
					$this->on_action_tab_next(true,true);
					$return = true;				
				}
				else if(bob::keyMod($e->state,Gdk::CONTROL_MASK)) {
					//. select tab on the right.
					$this->on_action_tab_next(true,false);
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Return): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. refocus on the text area.
					$this->curSrcView->view->grab_focus();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_F): { }
			case(Gdk::KEY_f): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. toggle search bar.
					$this->on_action_searchbar(true);
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_L): { }
			case(Gdk::KEY_l): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. create new file.
					$this->toolbar->btn['goto-line-bobLineJump']->entry->grab_focus();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_M): { }
			case(Gdk::KEY_m): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. create new file.
					$this->toolbar->btn['adjust-margin-bobAdjustMargin']->spin->grab_focus();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_N): { }
			case(Gdk::KEY_n): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. create new file.
					$this->mkNeuTab();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_R): { }
			case(Gdk::KEY_r): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. toggle replace bar.
					$this->on_action_replacebar(true,true);
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_S): { }
			case(Gdk::KEY_s): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. save current file.
					$this->on_action_save();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_W): { }
			case(Gdk::KEY_w): {
				if($e->state & Gdk::CONTROL_MASK) {
					//. close current window.
					$this->on_action_close();
					$return = true;
				}
				break;
			}
			default: {
				break;
			}
		}		
		bob::runUI();
		return $return;
	}
	
	public function on_autocomp_search($type,$word,$case = false) {
		$this->automatch = bob::$autocomp->search($type,$word);

		if(!$case)
			$this->autosearch = strtolower($word);
		else
			$this->autosearch = $word;

		if($this->automatchnum = count($this->automatch))
			$this->autocomp = true;
			
		return;
	}
	
	public function on_key_srcview($w,$e) {
		$return = false;
		
		if(!array_key_exists('keyval',$e)) {
			$e->keyval = 0;
		}
		
		switch($e->keyval) {
			case(Gdk::KEY_Up): {
				if($this->autocomp) {
					//. move up entry list.
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Down): {
				if($this->autocomp) {
					//. move down entry list.
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Return): {
				if($this->autocomp) {
					//. select an entry.
					$this->autocomp = false;
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Escape): {
				if($this->autocomp) {
					//. cancel selection.
					$this->autocomp = false;
					$return = true;
				}
				break;
			}
			default: {
		
				$iter = $this->curSrcView->current_iter();
				$this->autocomp = false;
		
				if($iter->ends_word()) {
					$type = 'primary';
					$start = $iter->copy();
					$backed = array(); $biter = 0;
					$looped = false;
		
					$start->backward_word_start();
					$backed = $start->backward_char();
					while(substr($this->curSrcView->buffer->get_text($start,$iter),0,1) == '_') {
						$start->backward_word_start();
						$backed = $start->backward_char();
						$looped = true;
					}
			
					if(!$backed)
						$start->backward_char();
					else
						$start->forward_char();
			
					if($this->signalAutoComp) {
						Gtk::timeout_remove($this->signalAutoComp);
						$this->signalAutoComp = null;
					}
			
					$this->signalAutoComp = Gtk::timeout_add(
						350,
						array($this,'on_autocomp_search'),
						$type,
						$this->curSrcView->buffer->get_text($start,$iter)
					);
				}
			}
		}
		
		return $return;
	}
	
	public function on_key_searchbox($w,$e) {
	
		//. default to enter so we can use the same method for the buttons
		//. as well as key activation.
		if(!$w && !$e) {
			$e = new StdClass;
			$e->keyval = Gdk::KEY_Return;
		}
	
		$return = false;
		bob::runUI();
		switch($e->keyval) {
			case(Gdk::KEY_Tab): {
				if($this->replacebar->is_visible()) {
					$this->replacebar->entry->child->grab_focus();
					$return = true;
				}
				break;
			}
			case(Gdk::KEY_Return): {
				$this->on_normal_search();
				$this->searchbar->update_history();
				$return = true;
				break;
			}
			case(Gdk::KEY_Escape): {
				$this->on_action_searchbar(false);
				$return = true;
				break;
			}
		}
		bob::runUI();
		return $return;
	}

	public function on_key_replacebox($w,$e) {
	
		if(!$w && !$e) {
			$e = new StdClass;
			$e->keyval = Gdk::KEY_Return;
		}
	
		$return = false;
		bob::runUI();
		switch($e->keyval) {
			case(Gdk::KEY_Return): {
				$this->on_live_replace();
				$this->searchbar->update_history();
				$this->replacebar->update_history();
				$return = true;
				break;
			}
			case(Gdk::KEY_Escape): {
				$this->on_action_searchbar(false);
				$return = true;
				break;
			}
		}
		bob::runUI();
		return $return;
	}

	public function on_action_quickop($force = null) {
		bob::runUI();
		if($force === true) {
			//. force on
			$this->toolbar->btn['quickop']->set_active(true);
			$this->quickopbar->show();
		} else if($force === false) {
			//. force off
			$this->toolbar->btn['quickop']->set_active(false);
			$this->quickopbar->hide();
			
			// this was added mostly to fix a rendering bug where text under
			// the currently highlighted line would disapear.
			if($this->curSrcView)
				$this->curSrcView->view->grab_focus();
					
		} else {
			//. do a toggle.
			if($this->quickopbar->is_visible()) {
				$this->toolbar->btn['quickop']->set_active(false);
				$this->quickopbar->hide();
				
				if($this->curSrcView)
					$this->curSrcView->view->grab_focus();
			} else {
				$this->toolbar->btn['quickop']->set_active(true);
				$this->quickopbar->show();
			}
		}
		
		return;
	}

	public function on_action_searchbar($force = null) {
		bob::runUI();
		if($force === true) {
			//. force on
			$this->toolbar->btn['search']->set_active(true);
			$this->searchbar->show();
			$this->searchbar->entry->child->grab_focus();		
		} else if($force === false) {
			//. force off
			$this->toolbar->btn['search']->set_active(false);
			$this->toolbar->btn['replace']->set_active(false);
			$this->searchbar->hide();
			$this->replacebar->hide();
			
			// this was added mostly to fix a rendering bug where text under
			// the currently highlighted line would disapear.
			if($this->curSrcView)
				$this->curSrcView->view->grab_focus();
					
		} else {
			//. do a toggle.
			if($this->searchbar->is_visible()) {
				$this->toolbar->btn['search']->set_active(false);
				$this->toolbar->btn['replace']->set_active(false);
				$this->searchbar->hide();
				$this->replacebar->hide();

				if($this->curSrcView)
					$this->curSrcView->view->grab_focus();
			} else {
				$this->toolbar->btn['search']->set_active(true);
				$this->searchbar->show();
				$this->searchbar->entry->child->grab_focus();		
			}
		}
		
		return;
	}

	public function on_action_replacebar($force = null,$magictrick = false) {
		bob::runUI();
		if($force === true) {
			$this->toolbar->btn['search']->set_active(true);
			$this->toolbar->btn['replace']->set_active(true);
			$this->replacebar->show();
			$this->searchbar->show();
			$this->searchbar->entry->child->grab_focus();
		} else if($force === false) {
			$this->toolbar->btn['replace']->set_active(false);
			$this->replacebar->hide();

			if($magictrick) {
				$this->toolbar->btn['search']->set_active(false);
				$this->searchbar->hide();				
			}

			if($this->curSrcView)
				$this->curSrcView->view->grab_focus();
				
		} else {
			if($this->replacebar->is_visible()) {
				$this->toolbar->btn['replace']->set_active(false);
				$this->replacebar->hide();
				
				if($magictrick) {
					$this->toolbar->btn['search']->set_active(false);
					$this->searchbar->hide();				
				}

				if($this->curSrcView)
					$this->curSrcView->view->grab_focus();
					
			} else {
				$this->toolbar->btn['search']->set_active(true);
				$this->toolbar->btn['replace']->set_active(true);
				$this->replacebar->show();
				$this->searchbar->show();
				$this->searchbar->entry->child->grab_focus();		
			}
		}
		
		return;
	}

	public function on_page_change($w,$ww,$page) {
		bob::runUI();
		$this->curPage = $page;
		$this->curSrcView = $this->notebook->get_nth_page($this->curPage);
		$this->curTabLabel = $this->notebook->get_tab_label($this->curSrcView); 
		$this->curFile = $this->curTabLabel->file;
		
		bob::runUI();
		
		$this->set_titlebar(basename($this->curFile));
		$this->on_margin_change($this->curSrcView->curMargin);
		$this->on_tab_change($this->curSrcView->curTabWidth);
		$this->on_eol_change($this->curSrcView->curEOL);
		
		bob::debugfln('page changed to %d %s',$this->curPage,$this->curFile);
		return;
	}

	public function on_page_reorder($w,$srcview,$page) {
		$this->on_page_change(null,null,$page);
		return;
	}

	public function on_recieve_drop($widget,$context,$x,$y,$data,$info,$time) {
		$entry = explode("\n",$data->data);
	
		foreach($entry as $file) {
			if(UNIX) {
				$file = preg_replace(
					'/^file:\/{1,2}(?:localhost)?/',
					'',
					urldecode(trim($file))
				);
			} else {
				$file = preg_replace(
					'/^file:\/\/\//',
					'',
					urldecode(trim($file))
				);		
			}

			if($file) {
				$this->openFile($file);
			}
		}
		
		return;
	}
	
	public function on_file_edit() {
		if($this->curSrcView->fresh) {
			bob::debugfln("file %s edited",basename($this->curFile));
			$this->curTabLabel->set_glow(true);
			$this->curSrcView->buffer->disconnect($this->curSrcView->signalEdit);
			$this->curSrcView->signalEdit = $this->curSrcView->fresh = false;
			bob::runUI();
		}
		return;
	}
	
	public function on_line_jump() {
		$line = (int)$this->toolbar->btn['goto-line-bobLineJump']->entry->get_text();
		$this->toolbar->btn['goto-line-bobLineJump']->entry->set_text('');
		$iter = $this->curSrcView->buffer->get_iter_at_line($line-1);
		$this->curSrcView->view->scroll_to_iter($iter,0);
		$this->curSrcView->buffer->place_cursor($iter);
		$this->curSrcView->view->grab_focus();
		return;
	}
	
	public function on_normal_search($highlight = true) {
		$query = $this->searchbar->entry->child->get_text();
		$mode = 0;
		$end = false;

		//. test mode selection drop down.
		switch($this->searchbar->case->get_active()) {
			case(1): {
				$mode += GtkSourceView::SEARCH_TEXT_ONLY;
				break;
			}
			default: {
				$mode += (GtkSourceView::SEARCH_CASE_INSENSITIVE | GtkSourceView::SEARCH_TEXT_ONLY);
				break;
			}
		}

		if($down = $this->searchbar->direction->get_active()) {
			//. going down.
			$found = GtkSourceView::forward_search(
				$start = $this->curSrcView->current_iter(),
				$query,
				$mode
			); unset($start);
			
			if(!$found) {
				$end = true;
				$found = GtkSourceView::forward_search(
					$start = $this->curSrcView->start_iter(),
					$query,
					$mode
				); unset($start);
			}			
		} else {
			//. going up.
			$found = GtkSourceView::backward_search(
				$start = $this->curSrcView->current_iter(),
				$query,
				$mode,
				$stop = $this->curSrcView->start_iter()
			); unset($start,$stop);
			
			if(!$found) {
				$end = true;
				$found = GtkSourceView::backward_search(
					$start = $this->curSrcView->end_iter(),
					$query,
					$mode,
					$stop = $this->curSrcView->start_iter()
				); unset($start,$stop);
			}	
		}
		
		if($found) {
			$found['end'] = $end;
			$found['down'] = $down;
			if($highlight) {
				//. this switcing of the selection range here places the
				//. cursor in a final resting place that makes good for
				//. continue in whichever diection.
				
				if($down)
					$this->curSrcView->buffer->select_range($found[1],$found[0]);
				else 
					$this->curSrcView->buffer->select_range($found[0],$found[1]);
					
				$this->curSrcView->view->scroll_to_iter($found[0],0);
				$this->curSrcView->view->scroll_to_iter($found[1],0);
				bob::runUI();
			}
		} else {
			$found = false;
		}
		
		return $found;
	}
	
	public function on_live_replace() {
		$neu = $this->replacebar->entry->child->get_text();
		$down = $this->searchbar->direction->get_active();

		switch($this->replacebar->type->get_active()) {
			case(1): {
				// replace all.				
				$count = 0;
				
				if($down)
					$this->curSrcView->buffer->place_cursor($this->curSrcView->start_iter());
				else
					$this->curSrcView->buffer->place_cursor($this->curSrcView->end_iter());
				
				$this->curSrcView->buffer->begin_user_action();
				while($found = $this->on_normal_search(false)) {
					if($found['end'] == true) { break; }
					
					$this->curSrcView->buffer->delete($found[0],$found[1]);
					$this->curSrcView->buffer->insert($found[0],$neu);
					$this->curSrcView->buffer->place_cursor(
						$this->curSrcView->buffer->get_iter_at_offset(
							$found[0]->get_offset() + (($down)?(strlen($neu)):(strlen($neu)*-1))
						)
					); unset($found[0],$found[1]);

					if($count % 50 == 0) {
						$this->statusbar->message('viewer',$count);
						bob::runUI();
					} ++$count;
				}
				$this->curSrcView->buffer->end_user_action();
		
				$this->statusbar->message('main',sprintf(
					'Replaced %d Instance%s',
					$count, (($count === 1)?(''):('s'))
				)); break;
			}
			default: {
				if($found = $this->on_normal_search(false)) {
					$this->curSrcView->buffer->delete($found[0],$found[1]);
					$this->curSrcView->buffer->insert($found[0],$neu);
					$this->curSrcView->buffer->place_cursor(
						$this->curSrcView->buffer->get_iter_at_offset(
							$found[0]->get_offset() + (($down)?(strlen($neu)):(strlen($neu)*-1))
						)
					); unset($found[0],$found[1]);				
				} break;
			}
		}
	}

	public function on_resize($w,$e) {
		list($w,$h) = $this->get_size();
		list($x,$y) = $this->window->get_position();

		if($w == $this->width && $h == $this->height && $x == $this->x && $y == $this->y) {
			return;
		}
		
		$this->width = $w;
		$this->height = $h;
		$this->x = $x;
		$this->y = $y;
		
		bob::$config->set('window-width',$w);
		bob::$config->set('window-height',$h);
		bob::$config->set('window-pos-x',$x);
		bob::$config->set('window-pos-y',$y);
	}

	public function on_close() {
		$this->hide();
		if(bob::isLastWindow()) {
			bob::debugfln('last window closed, terminating program');
			bob::endProgram();
		} else {
			bob::windowUnregister($this);
			Gtk::timeout_add(
				1000,
				array('bob','destroyWindow'),
				$this
			);
		}
		return;
	}
	
	public function set_titlebar($string) {
		$this->set_title(sprintf(
			'%s%s',
			(($string)?
				("{$string} - "):
				('')),
			PROGRAM
		));
	}
}

?>